package com.dksidana.java.pool.impl;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;

import com.dksidana.java.pool.ObjectTimestampPair;
import com.dksidana.java.pool.Pool;
import com.dksidana.java.pool.PoolableObjectFactory;

public class GenericPool<K,E extends Exception> implements Pool<K,E> {

	private boolean testOnBorrow=true;
	private int maxTotal=-1;
	ConcurrentHashMap<K, Long> locked=new ConcurrentHashMap<K, Long>();
	LinkedBlockingQueue<ObjectTimestampPair<K>> unlocked=new LinkedBlockingQueue<ObjectTimestampPair<K>>();
	PoolableObjectFactory<K,E> factory;

	public GenericPool(PoolableObjectFactory<K,E> factory) {
		this.factory=factory;
	}
	public K borrowObject() throws E {
		K k=pollForIdleObject();
		if(k!=null)
			return k;
		if(maxTotal<=0 || locked.size()+unlocked.size()<maxTotal)
		{
			// no objects available, create a new one
			return createAndReady();
		}
		else
		{
			return waitForever();
		}
	}
	private K pollForIdleObject() throws E
	{
		while(true)
		{
			ObjectTimestampPair<K> pair=unlocked.poll();
			if(pair==null)
				break;
			pair=getReady(pair,true);
			if(pair!=null)
				return pair.getObject();		
		}
		return null;
	}
	private K createAndReady() throws E
	{
		K k = factory.makeObject();        
		getReady(new ObjectTimestampPair<K>(k,System.currentTimeMillis()), false);
		return k;
	}
	private K waitForever() throws E
	{
		while(true)
		{
			try {
				ObjectTimestampPair<K> pair = unlocked.take();
				pair=getReady(pair,testOnBorrow);
				if(pair!=null)
					return pair.getObject();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}		
	}
			
	public void returnObject(K k) throws E {
		locked.remove(k);
		if(!unlocked.offer(new ObjectTimestampPair<K>(k)))
		{
			factory.detroyObject(k);
		}
		
	}
	/*
	 * make the object ready to be return. validate it, if (valid) then put in locked set, else destroy object
	 * 
	 */
	private ObjectTimestampPair<K> getReady(ObjectTimestampPair<K> k,boolean validate) throws E
	{
		if(!validate||factory.validateObject(k))
		{
			//(expirationTime<=0L || (now - unlocked.get(k).longValue()) < expirationTime)
			locked.put(k.getObject(),System.currentTimeMillis());
			return k;
		}
		else
		{
			factory.detroyObject(k.getObject());
			return null;
		}
	}
	public void invalidateObject(K k) throws E {
		factory.detroyObject(k);
	}
	
}
